﻿' Code created by Edwin Landy October 2009.  Creative Commons License or GNU public license Applies
' http://creativecommons.org/licenses/by-nc-nd/3.0/

Imports Google.Documents
Imports Google.GData.Documents
Imports Google.GData.Client
Imports Google.GData.Apps

Public Class Form1
    Private _docsService As New DocumentsService("GDocsOAuthBackup")
    Private _dir As System.IO.DirectoryInfo
    Private _requestCount As Integer = 0
    Private _googleUnavailableCount As Integer = 0
    Private _logBuilder As New System.Text.StringBuilder()
    Private _listOfFiles As New Collections.Generic.List(Of String)
    Private _pdfCount As Integer = 0
    Private _domain As String
    Private _key As String
    Private _secret As String
    Private _adminEmail As String
    Private _adminPassword As String


    Private Sub Button_Go_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button_Go.Click
        _logBuilder.AppendLine(Now.ToString & " Backup Started.")

        _domain = Text_Admin_Email.Text.Substring(Text_Admin_Email.Text.IndexOf("@") + 1)
        _key = Text_OAuthKey.Text
        _secret = Text_OAuthSecret.Text
        _adminEmail = Text_Admin_Email.Text
        _adminPassword = Me.Text_Admin_Password.Text

        Try
            _dir = New System.IO.DirectoryInfo(Text_Backup_Directory.Text)
            If Not _dir.Exists Then
                _dir.Create()
            End If
        Catch ex As Exception
            MessageBox.Show("Cannot Create Directory. Please check directory and try again.")
            Exit Sub
        End Try

        If Not _dir.ToString.Substring(_dir.ToString.Count).Contains("\") Then
            _dir = New System.IO.DirectoryInfo(_dir.FullName & "\")

        End If
        Dim backedupDocs As New Collections.Generic.List(Of String)
        Dim documentFormat As String

        Select Case Combo_Docs_Export_Type.SelectedItem.ToString
            Case "Open Office Document (.odt)"
                documentFormat = "odt"
            Case "Microsoft Word (.doc)"
                documentFormat = "doc"
            Case "Rich Text (.rtf)"
                documentFormat = "rtf"
            Case Else
                documentFormat = "odt"
        End Select

        Dim spreadsheetFormat As String
        Select Case Combo_Spreadsheets_Export_Type.SelectedItem.ToString
            Case "Open Office Spreadsheet (.ods)"
                spreadsheetFormat = "ods"
            Case "Microsoft Excel (.xls)"
                spreadsheetFormat = "xls"
            Case Else
                spreadsheetFormat = "ods"
        End Select
        Button_Go.Enabled = False
        Dim OARequestFactory As New GOAuthRequestFactory("cl", "GDocsOAuthBackup")
        OARequestFactory.ConsumerKey = _key
        OARequestFactory.ConsumerSecret = _secret

        _docsService.RequestFactory = OARequestFactory

        Dim appsService As New AppsService(_domain, _adminEmail, _adminPassword)
        ' Google seems to want only 100 requests at a time, because
        Request_Counter()
        Write_To_Tool_Strip("Getting List of Users.")
        Try
            Dim userFeed As UserFeed = appsService.RetrieveAllUsers
            For Each u As UserEntry In userFeed.Entries
                Dim docListQuery As New DocumentsListQuery()
                docListQuery.UseSSL = True
                docListQuery.OAuthRequestorId = u.Login.UserName & "@" & _domain
                Request_Counter()
                Write_To_Tool_Strip("Fetching List of Docs for " & u.Login.UserName & ".")
                Dim docsFeed As New Feed(Of Document)(_docsService, docListQuery)

                For Each d As Document In docsFeed.Entries
                    If Not backedupDocs.Contains(d.DocumentId) Then
                        Dim strUri As String = ""
                        Dim strFormat As String = ""
                        If d.Type = Document.DocumentType.Document Then
                            strUri = "https://docs.google.com/feeds/download/documents/Export?docID=" & d.DocumentId & "&exportFormat=" & documentFormat
                            Export_Doc(documentFormat, strUri, d.Title, docListQuery.OAuthRequestorId)
                        ElseIf d.Type = Document.DocumentType.Spreadsheet Then
                            strUri = "https://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=" & d.DocumentId & "&exportFormat=" & spreadsheetFormat
                            Export_Doc(spreadsheetFormat, strUri, d.Title, docListQuery.OAuthRequestorId)
                        ElseIf d.Type = Document.DocumentType.Presentation Then
                            strUri = "https://docs.google.com/feeds/download/presentations/Export?docID=" & d.DocumentId & "&exportFormat=ppt"
                            Export_Doc("ppt", strUri, d.Title, docListQuery.OAuthRequestorId)
                        End If
                        If d.Type = Document.DocumentType.PDF Then _pdfCount += 1
                        backedupDocs.Add(d.DocumentId)
                    End If
                Next
                docsFeed = Nothing
                docListQuery = Nothing
            Next
        Catch credex As InvalidCredentialsException
            _logBuilder.AppendLine(credex.Message)
            MessageBox.Show("Invalid credentials.  Check your admin email address and password and try again.", "Invalid Creds")

        Catch reqex As GDataRequestException
            _logBuilder.AppendLine("Error - " & vbLf & reqex.InnerException.Message)
            If reqex.InnerException.Message = "The remote server returned an error: (401) Unauthorized." Then
                MessageBox.Show("Invalid Token.  Check your consumer key and consumer secret and try again.")
            End If

        Catch ex As Exception
            _logBuilder.AppendLine(ex.Message)
        End Try
        _logBuilder.AppendLine(Now.ToString & " Backup Completed - backed up " & (backedupDocs.Count - _pdfCount).ToString & " documents.")
        Write_Log()
        Write_To_Tool_Strip("Done. See backup log in backup directory for more information.")
        Button_Go.Enabled = True
    End Sub

    Private Sub Export_Doc(ByVal format As String, ByVal uri As String, ByVal title As String, ByVal requestorID As String)
        ' Remove invalid characters from document title.
        Dim invalidCharacters() As Char = System.IO.Path.GetInvalidFileNameChars
        For Each c As Char In invalidCharacters
            title = title.Replace(c, "")
        Next

        title.Trim()

        ' Users may have named a file exactly the same as another user.
        ' Append the username to files that have the same title.

        If _listOfFiles.Contains(title) Then
            title = title & "(" & requestorID.Substring(0, requestorID.IndexOf("@")) & ")"
        End If

        ' Users may have multiple files with the same name.
        ' Add a number to the back of the title if this is the case.
        title = Get_Unique_FileName(title, 1)

        _listOfFiles.Add(title)

        Dim fileName As String = _dir.FullName & title & "." & format


        Dim file As New System.IO.FileStream(fileName, IO.FileMode.Create)
        Dim str As System.IO.Stream
        Dim q As New Google.GData.Client.DocumentQuery(uri)
        q.UseSSL = True
        q.OAuthRequestorId = requestorID

        Try

            Request_Counter()
            Write_To_Tool_Strip("Fetching Document - " & title & " of user " & requestorID)

            str = _docsService.Query(q.Uri)

            Dim buffer(2048) As Byte
            Dim count As Integer = 0

            Write_To_Tool_Strip("Writing Document - " & fileName & ".")
            Do
                count = str.Read(buffer, 0, buffer.Length)
                file.Write(buffer, 0, count)
            Loop While count > 0


        Catch reqex As GDataRequestException

            _logBuilder.AppendLine("Failed to backup '" & title & "' - Request Exception - " & reqex.ResponseString)

        Catch nogood As Google.GData.Client.ServiceUnavailableException

            If _googleUnavailableCount >= 10 Then
                _logBuilder.AppendLine("Got Service Unavailable error 10 times... giving up.")
                Write_Log()
                Exit Sub

            Else
                _logBuilder.AppendLine("Failed to backup '" & title & "' - service unavailable.  Waiting a second before retry.")
                _googleUnavailableCount += 1
                Threading.Thread.Sleep(1000)
                Export_Doc(format, uri, title, requestorID)

            End If

        Catch ex As Exception
            _logBuilder.AppendLine("Failed to backup '" & title & "' - general exception.  " & ex.Message)
        Finally

            file.Flush()
            file.Close()
            If Not str Is Nothing Then str.Close()
        End Try
    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Combo_Docs_Export_Type.SelectedIndex = 0
        Combo_Spreadsheets_Export_Type.SelectedIndex = 0

    End Sub

    Private Sub Write_Log()
        IO.File.AppendAllText(_dir.FullName & "backup_log_" & Format(Now, "yyyyMMddhhmm") & ".log", _logBuilder.ToString)
    End Sub
    Private Sub Write_To_Tool_Strip(ByVal text As String)
        Me.ToolStripStatusLabel1.Text = text
        Me.StatusStrip1.Refresh()
    End Sub

    Private Sub Request_Counter()
        If _requestCount >= 100 Then
            Write_To_Tool_Strip("100 requests to Google - waiting for 10 seconds.")
            Threading.Thread.Sleep(10000)
            _requestCount = 0
        Else
            _requestCount += 1

        End If
    End Sub

    ' See if the file name about to be written exists, and get a new file name if it does.
    Private Function Get_Unique_FileName(ByVal fileName As String, ByVal startingIndex As Integer) As String
        Dim fn As String = fileName

        If Not _listOfFiles.Contains(fileName) Then
            Return fileName
        ElseIf Not _listOfFiles.Contains(fileName & "(" & startingIndex.ToString & ")") Then
            Return fileName & "(" & startingIndex.ToString & ")"
        Else
            Return Get_Unique_FileName(fileName, startingIndex + 1)
        End If

    End Function
End Class
